﻿using System;
using System.Collections.Generic;
using System.IO;

namespace yacomp.os
{
    using SanlieHash.Fast;
    using yacomp.algo;
    using yacomp.helpers;
    using yacomp.structs;

    public class YacompStorage : IDisposable
    {
        internal Dictionary<string, YacompFile> file_map;
        internal Dictionary<string, YacompPath> path_map;
        internal YacompPath root_path;
        internal string now_path;
        public YacompStorage()
        {
            file_map = new Dictionary<string, YacompFile>();
            path_map = new Dictionary<string, YacompPath>();
            root_path = new YacompPath(this);
        }
        public void CopyTo(YacompStorage storage2)
        {
            foreach (var path in path_map) storage2.path_map[path.Key] = path.Value.CopyTo(storage2);
            foreach (var file in file_map) storage2.file_map[file.Key] = file.Value.CopyTo(storage2);
            foreach (var path in storage2.path_map) path.Value.ParentPath().link_path(path.Value.ID); 
            foreach (var file in storage2.file_map) file.Value.ParentPath().link_file(file.Value.ID); 
        }
        public IEnumerable<YacompFile> LoopFiles()
        {
            var files = new Stack<YacompFile>();
            helper.CollectToStack(files, file_map.Values);
            while (files.Count != 0)  yield return files.Pop();
        }
        public IEnumerable<YacompPath> LoopPaths()
        {
            var paths = new Stack<YacompPath>();
            helper.CollectToStack(paths, path_map.Values);
            while (paths.Count != 0)  yield return paths.Pop();
        }
        internal string calc_id(string path)
        {
            path = ToAbsolutePath(path);
            return FastGuoMi.GetSM3(path);
        }
        public void Goto(string path)
        {
            path = ToAbsolutePath(path);
            if (Exist(path)) now_path = path;
            else throw new ArgumentNullException(path);
        }
        public int CalcFileCount() { return file_map.Count; }
        public int CalcPathCount() { return path_map.Count; }
        public bool Exist(string path)
        {
            var hash = calc_id(path);
            if (file_map.ContainsKey(hash)) return true;
            if (path_map.ContainsKey(hash)) return true;
            return false;
        }
        public bool IsFile(string path)
        {
            var hash = calc_id(path);
            if (file_map.ContainsKey(hash)) return true;
            return false;
        }
        internal bool exist_hash(string path_hash)
        {
            if (file_map.ContainsKey(path_hash)) return true;
            if (path_map.ContainsKey(path_hash)) return true;
            return false;
        }
        public string ToAbsolutePath(string path)
        {
            return helper.to_absolute_path(path, now_path);
        }
        public YacompFile OperateFile(string fullpath, bool leave_open = false)
        {
            fullpath = ToAbsolutePath(fullpath);
            var hash = FastGuoMi.GetSM3(fullpath);
            if (file_map.ContainsKey(hash)) return file_map[hash];

            file_map[hash] = new YacompFile(new struct_yacomp_file { id = hash, filename = fullpath }, this, leave_open);
            var pathname = helper.read_pathname(fullpath);
            make_paths(pathname);
            FindPath(pathname).link_file(hash);
            return file_map[hash];
        }
        public YacompPath OperatePath(string fullpath)
        {
            fullpath = ToAbsolutePath(fullpath);
            var hash = FastGuoMi.GetSM3(fullpath);
            if (path_map.ContainsKey(hash)) return path_map[hash];

            path_map[hash] = new YacompPath(new struct_yacomp_path { id = hash, pathname = fullpath }, this);
            make_paths(fullpath);
            return path_map[hash];
        }
        public YacompPath FindPath(string path = null)
        {
            path = ToAbsolutePath(path);
            if (helper.is_root_path(path)) return root_path;

            if (Exist(path)) return OperatePath(path);
            throw new ArgumentNullException(path);
        }
        public YacompFile FindFile(string path)
        {
            path = ToAbsolutePath(path);
            if (IsFile(path)) return OperateFile(path);
            return null;
        }
        internal void make_paths(string path)
        {
            if (helper.is_root_path(path)) return;
            var pre_hash = string.Empty;
            var hash = string.Empty;
            YacompPath ref_now;
            hash = FastGuoMi.GetSM3(path);
            if (!path_map.ContainsKey(hash)) path_map[hash] = new YacompPath(new struct_yacomp_path {id = hash, pathname = path }, this);
            pre_hash = hash;

            path = helper.read_before_pathname(path);
            while (!helper.is_root_path(path))
            {
                hash = FastGuoMi.GetSM3(path);
                if (path_map.ContainsKey(hash)) ref_now = path_map[hash];
                else ref_now = path_map[hash] = new YacompPath(new struct_yacomp_path { id = hash, pathname = path }, this);

                ref_now.link_path(pre_hash);
                pre_hash = hash;
                path = helper.read_before_pathname(path);
            }
        }
        public void RemovePath(string path)
        {
            if (!Exist(path)) return;
            if (IsFile(path)) return;

            var p = FindPath(path);
            Remove(p);
        }
        public void RemoveFile(string path)
        {
            if (!IsFile(path)) return;

            var hash = calc_id(path);
            if (file_map.ContainsKey(hash)) file_map.Remove(hash);
        }

        public void Remove(YacompPath p)
        {
            if (!path_map.ContainsKey(p.ID)) return;
            path_map.Remove(p.ID);
            var queue = new Queue<YacompPath>();
            queue.Enqueue(p);

            while (queue.Count > 0)
            {
                var _del = queue.Dequeue();
                foreach (var _p in _del.LoopPaths())
                {
                    path_map.Remove(_p.ID);
                    queue.Enqueue(_p);
                }

                foreach (var _f in _del.LoopFiles()) file_map.Remove(_f.ID);
            }
        }
        public void Remove(YacompFile f)
        {
            if (!file_map.ContainsKey(f.ID)) return;
            file_map.Remove(f.ID);
        }

        public void AddFile(string localpath)
        {
            localpath = Path.GetFullPath(localpath);
            if (!File.Exists(localpath)) return;
            var filename = helper.to_absolute_path(helper.read_filename(localpath), now_path);
            var yacomp_f = OperateFile(filename);

            yacomp_f.info.fullpath_localos = localpath;
            yacomp_f.info.time_edit = File.GetLastWriteTime(localpath);
            yacomp_f.info.compress_tag = false;
            yacomp_f.info.compress_size = 0;
            using (var f = File.Open(localpath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            {
                f.Position = 0;
                yacomp_f.info.crc32 = helper.calc_crc(f);
                yacomp_f.info.not_compress_size = (uint)f.Length;
            }
            yacomp_f.info.offset = 0;
            yacomp_f.info.data_offset = 0;
        }
        public YacompFile AddFile(string filepath, Stream s)
        {
            var filename = helper.to_absolute_path(helper.read_filename(filepath), now_path);
            var yacomp_f = OperateFile(filename);

            yacomp_f.info.not_compress_mem = s;
            yacomp_f.info.time_edit = DateTime.Now;
            yacomp_f.info.compress_tag = false;
            yacomp_f.info.compress_size = 0;
            // crc
            s.Position = 0;
            yacomp_f.info.crc32 = helper.calc_crc(s);
            yacomp_f.info.not_compress_size = (uint)s.Length;
            yacomp_f.info.offset = 0;
            yacomp_f.info.data_offset = 0;
            return yacomp_f;
        }
        public YacompFile AddFilebyPath(string filepath, Stream s)
        {
            var now_path = this.now_path;
            this.now_path = helper.read_pathname(filepath);
            var file = AddFile(filepath, s);
            this.now_path = now_path;
            return file;
        }
        public void CreatePath(string pathname)
        {
            var p = OperatePath(pathname);
            p.info.time_edit = DateTime.Now;
        }
        internal void reset_algo(api_compress_algo algo)
        {
            foreach (var info in LoopFiles()) info.info.algo = algo;
        }
        internal bool is_disposed { get; set; }
        public void Dispose()
        {
            this.is_disposed = true;
            var needrm_keys = new Stack<string>();
            string key;
            helper.CollectToStack(needrm_keys, file_map.Keys);
            while (needrm_keys.Count != 0) if (file_map.ContainsKey(key = needrm_keys.Pop())) file_map.Remove(key);
            helper.CollectToStack(needrm_keys, path_map.Keys);
            while (needrm_keys.Count != 0) if (file_map.ContainsKey(key = needrm_keys.Pop())) file_map.Remove(key);
            this.file_map.Clear();
            this.path_map.Clear();
            this.root_path.inner_dispose();
        }
    }
}
